# 函数
# 定义：将具有独立功能的代码块组织成一个整体，使其具有特殊功能的代码集。
# 作用：使用函数可以加强代码的复用性，提高程序编写的效率。
# 结构：def 函数名():
#         函数体
# 注意：函数名要符合标识符规定，最好见名知意。

def login():
    print("登录......")


login()


def say_hello():
    print("hello")
    print("welcome")


say_hello()


# 注意调用函数前，函数必须存在

# return 返回值

def buy():
    return "烟"


print("买了：", buy())


def price():
    return "烟", 20  # return 返回多个返回值，以元组的形式返回给调用者


print(price())


# 参数
def add():
    a = 1
    b = 2
    return a + b


print(add())


# 定义格式：
# def函数名（形参a,形参b):
#     函数体
#     ..（如a=1 b=2)
# 调用格式：
# 函数名（实参1，实参2）

def add(a, b):
    return a + b


print(add(1, 2))


# 必备参数
# 传递和定义参数的顺序及个数必须一致,方法定义有几个参数，调用的时候就必须按照顺序传入几个参数
# def func(a,b)：
# 。。。
# func("a","b")

# 默认参数
# 位参数提供默认值 ，调用函数时候可以不传该默认参数的值
# 注意，所有的位置参数必须出现在默认参数前，包括函数定义和调用
# 格式：def func(a=20):

def funb(a=8):
    print(a)


funb()  # 8 没有传值会根据默认值执行代码
funb(200)  # 200 传了值就使用传入的值执行代码


# 可变参数
# 传入的值的数量是可以改变的，可以传入多个，也可以不传,已元组的形式接收
# 格式：def func(*args) #args可以改成其它
def func(*args):
    print(args)


func(1)


# 关键字参数
# 格式：def func(**kwargs)
# 作用：可以扩展函数的功能

def fund(**kwargs):
    print(kwargs)


fund()  # {} 返回空字典
fund(name='bingbing', age=18)  # 传值的时候需要采用键=值的形式


# 函数嵌套
# 嵌套调用
# def study():
#     print("晚上在学习")
#
# def course():
#     print("python 基础")
#
# def student():
#     study()
#     course()
#
# student()

def study():
    print("晚上在学习")

    def course():
        print("python 基础")

    course()


study()

# 作用域
# 含义：变量生效的范围，分全局变量和局部变量
# 全局变量：
# 函数外部定义的变量，在整个文件中都是有效的
a = 100


def tst():
    print("这是test中a的值：", a)


tst()


def tes():
    a = 120
    print("这是test中a的值：", a)


tes()


# 局部变量
# 函数内部定义的变量，从定义位置开始，到函数定义结束位置有效
def funa():
    num = 10  # 局部变量
    a = 200
    print(num)
    print(a)


funa()
# print("num",num) # 报错，局部变量只能在定义该函数的内部使用，外部不能使用
print(a)


# global关键字
# 将变量声明为全局变量
# 语法:glabal 变量名

def te2():
    global name  # 将局部变量a生命为全局变量
    global age, sex  # 生命多个全局变量
    name = 300
    age = 18
    sex = '男'
    print(name)


te2()
print(name)
print(age)
print(sex)

# nonlocal --了解
# 用来声明外层的局部变量，只能在嵌套中使用，在外部函数先进行声明，内部函数进行nonlocal声明
a = 10


def outer():  # 外函数
    a = 5  # 局部变量

    def inner():  # 内函数
        a = 20

        def inner2():
            nonlocal a
            a = 30
            print("inner2函数的a值：", a)  # 30

        inner2()
        print("inner函数中a的值：", a)  # 30

    inner()
    print("outer函数中a的值：", a)  # 5


outer()


# 匿名函数
# 语法：函数名=lambda 形参：返回值
# lambda是定义匿名函数的关键字，相当于函数的def。
# 调用：结果=函数名（实参)

def add(a, b):
    return a + b


print(add(1, 2))

add = lambda a, b: a + b
print(add(1, 2))

# lambda参数形式
# 函数名 = lambda 形参：返回值（表达式）

# 无参数
funa = lambda: "一杯茶"
print(funa())

# 一个参数
func = lambda name: name
print(func("bingbing"))

# 默认参数
fund = lambda name, age=18: (name, age)
print(fund("bingbing"))
# 默认参数必须写在非默认参数后边

# 关键字参数
fune = lambda **kwargs: kwargs
print(fune(name='bingbing', age=18))

# lambda结合if判断
a = 5
b = 8
print("a比b小") if a < b else print("a比b大")

comp = lambda a, b: "a比b小" if a < b else "a比b大"
print(comp(a, b))
# lambda只能实现简单的逻辑，如果逻辑复杂且代码量较大，不建议使用lambda,降低代码的可读性，为后期的代码维护增加困难

# 内置函数
# 查看所有的内置函数
import builtins

print(dir(builtins))
# 大写字母开头一般是内置常量名，小写字母开头一般是内置函数名

# abs() 返回绝对值
print(abs(-10))  # 10
print(abs(10))  # 10

# sum() 求和
# print(sum(123)) 整型不是可迭代对象 ，报错
print(sum([1, 2, 3]))
print(sum((1, 2, 3)))
print(sum({1, 2, 3}))

# min() 求最小值
print(min(4, 1, 5))
print(min(-8, 5, key=abs))  # 5

# zip() 将可迭代对象作为参数，将对象中的对应的元素打包成一个个元组
li = [1, 2, 3]
li2 = ['a', 'b', 'c']
print(zip(li, li2))

for i in zip(li, li2):
    print(i)  # 如果元素个数不一致，就按照长度最短的返回

print(list(zip(li, li2)))  # 转换成列表打印 必须是可迭代对象

# map(func,iter) 可以对可迭代对象中的每一个元素进行映射，分别取执行,func自己定义的函数，iter要放进去的可迭代对象
# 对象中的每一个元素都回去执行这个函数
li = [1, 2, 3]
# def funa(x):
#     return x * 5
funa = lambda a: a * 5
mp = map(funa, li)  # 只要写函数名，不用写大括号
print(mp)
print(list(mp))

# deduce() 先把对象中的两个元素取出来，计算出一个值然后保存，接着把这个计算值跟第三个元素进行计算
# 需要先导包
from functools import reduce

# reduce(function, sequence, initial=_initial_missing)
# function 必须是有两个参数的函数
# sequence 必须是可迭代对象
li = [1, 2, 3]


def add(x, y):
    return x + y


res = reduce(add, li)
print(res)
print(sum(li))

# 拆包
# 拆包：对于函数中的多个返回数据，去掉元组，列表或者字典直接获取里面数据的过程。
tua = (1, 2, 3, 4)
print(tua)
print(tua[0])
a, b, c, d = tua
print("a=", a, "b=", b, "c=", c, "d=", d)
# 要求元祖内的个数与接收个数相同

# 先把单独的取完，其它的都交给带*的变量
a, *b = tua
print(a, b)
c, d, e = b
print(c, d, e)


def funa(a, b, *args):
    print(a, b)
    print(args, type(args))


funa(1, 2, 3, 4, 5, 6, 7)

arg = (1, 2, 3, 4, 5, 6, 7)
funa(*arg)


# 抛出异常raise
# 步骤：
# 1.创建一个Exception('xxx')对象，xxx-异常提示信息
# 2.raise抛出这个对象（异常对象）

# raise Exception("binbing 抛出了一个异常") # Exception: binbing 抛出了一个异常
# def funa():
#     print("hahaha 笑死我了")
#     raise Exception("binbing 抛出了一个异常") # Exception: binbing 抛出了一个异常
# funa()

def login():
    pwd = input("请输入密码：")
    if len(pwd) >= 6:
        return "密码输入成功"
    raise Exception("长度不足六位")


# print(login())

# try:
#     print(login())
# except Exception as e:
#     print(e)

# 模块
# 含义：一个py文件就是一个模块，即导入模块本质上就是执行一个py文件
# 内置模块
# 如：random、time、os、logging、直接导入即可
import time

# 第三方模块（第三方库）
# 下载：cmd窗口输入pip install 模块名
# 安装：pip install 模块名
# 卸载：pip unindysll 模块名

# 自定义模块：即自己定义的项目中的模块
# 注意：命名要遵循标识符及变量命名规范，并且不要与内置模块器冲突

# 导入模块
# import 模块名1,模块名2...
# 调用功能：模块名.功能名
import pytest

# 调用变量
print(pytest.name)
# 调用函数
pytest.funa()

# 从模块中导入指定的部分
# 语法：from 模块名 import 功能名1，功能名2...
from pytest import funb, name

funb()
print(name)

# 导入模块中所有内容
# from 模块名 import *
from pytest import *

funa()
funb()
print(name)

# as 给模块起别名
# 语法：import 模块名 as 别名
import pytest as pt

pt.funa()

# as 给功能起别名
# 语法：from 模块名 import 功能名 as 别名
from pytest import funb as fb, name

fb()
print(name)

# 内置全局变量__name__
# if __name__ == "__main__":
# 作用：用来控制py文件在不同的应用场景执行不同的逻辑
# 1.文件在当前程序执行（即自己执行自己）：__name__ == "__main__"
# 2.文件被当做模块被其它文件导入：__name__ == 模块名


# 包
# 含义：就是项目结构中的文件夹/目录，将有联系的模块放到同一个文件夹下，并且在这个文件夹中创建一个名字为init .py文件，那么这个文件夹就称之为包，有效避免模块名称冲突问题让结构更清晰。
# 与普通文件夹区别：
# 包包含__init__.py的文件夹
# import导入包时，首先会执行__init__.py文件的代码，尽量保证init文件的简单
# 导包方式1
import pythonstudy.pack01
# 导包方式2
# from pack01 import register
# register.reg()

# 注意：
# 1.import导入包时，首先执行__init__.py文件的代码
# 2.不建议在init中编写过多代码，尽量保证init的内容简单
# 3.__all__变量：
# 一个列表，可以控制要引入的东西（模块、函数、类等）
# 4.包的本质依然是模块，包又可以包含包

from pythonstudy.pack01 import *

register.reg()
login.login()


# 递归函数
# 含义：如果一个函数在内部不调用其他的函数，而是调用它本身的话，这个函数就是递归函数。
# 条件：
# 1.明确的结束条件
# 2.每进行更深一层的递归时，问题规模相比上次递归都要有所减少。
# 3.相邻两次重复之间有紧密的联系。

def add():
    s = 0
    for i in range(1, 101):
        s += i
    print(s)


add()


# 递归实现
def add2(n):
    if n == 100:
        return 100
    return n + add2(n + 1)


print(add2(1))


# 斐波那契数列：
# 1,1,2,3,5,8,13.,..
# 规律：从第三项开始，每一项都等于前两项之和，即n(n-2)+(n-1)n一当前项，n-1一前一项n-2一前前一项

def fun(n):
    if n <= 1:
        return n
    return fun(n - 2) + fun(n - 1)


print(fun(3))


# 闭包
# 含义：在嵌套函数的前提下，内部函数使用了外部函数的变量，而且外部函数返回了内部函数，我们就把使用了外部函数变量的内部函数称为闭包。
# 1.函数嵌套（函数里面再定义函数
# 2.内层函数使用外层函数的局部变量
# 3.外层函数的返回值是内层函数的函数名

# 简单的闭包函数：
def outer():
    n = 10

    def inner():
        print(n)

    return inner


print(outer())  # <function outer.<locals>.inner at 0x00000230A72A88B0>

# 第一种
outer()()  # 10
# 第二种
out = outer()
out()  # 10


def ot(m):
    n = 10

    def inner(o):
        print("计算结果：", n + m + o)

    return inner  # 返回函数名，不带小括号，因为inner函数里边参数比较多时或者受到限制时，写法不太规范


o = ot(20)
o(10)


# from pymediainfo import MediaInfo
#
# media_info = MediaInfo.parse('D:\\opt\\app\\ics\\yw_video\\live\\q2\\222.mp4')
# data = media_info.to_json()
# print(data)  # isstreamable 是yes的时候表示MP4文件是启用了FastStart（即MOOV原子前置）

# 函数引用
def funa():
    print(123)


print(funa)  # 函数名里边保存了函数所在位置的内存地址的引用
print("------------------")


# 每次开启内函数都在使用同一份闭包变量
def outer(m):
    print("outer()函数中的值：", m)

    def inner(n):
        print("inner()函数中的值：", n)
        return m + n

    # 在inner函数中返回m+n的值
    return inner


ot = outer(10)  # 共调用外函数：给outer()传值
print(ot)
print(ot(20))  # 调用内函数，给inner()传值
print(ot(30))
print(ot(40))


# 装饰器
# 含义：
# 装饰器本质上是一个Python函数，它可以让其他函数在不需要做任何代码变动的前提下增加额外功能，装饰器的返回值也是一个函数对象。
# 装饰器需要满足以下两点：
# 1.不修改原程序或函数的代码
# 2.不改变函数或程序调用方法。


# def tt02():
#     print("发送信息给zhangsan")
#
#
# def tt03(fn):
#     print("开始注册")
#     fn()  # 调用要传入的参数
#
#
# tt03(tt02)

def send():
    print("发送消息")


# send()

def outer(fn):
    def inner():
        fn()

    return inner


# outer(send)()
ot = outer(send)
ot()


# 语法糖
# 函数装饰器的语法：
# def装饰器函数名（参数）：
#     语句块
#     return 函数对象
# @装饰器函数名<换行>
#     def 函数名（参数列表）：
#         语句块

def outer(fn):
    def inner():
        print("登录")
        fn()

    return inner


@outer
def send():
    print("发送消息：哈哈哈，，，，，")


send()

print("----------------------")
def outer(fn):
    def inner(name):
        print(f"{name}是inner函数中的参数")
        print("登录")
        fn(name)

    return inner


@outer
def send1(name):
    print("发送消息：哈哈哈，，，，，")


send1('bingbing')

#1.5被装饰的函数有可变参数*args,**kwargs

# 被装饰的函数
def func(*args,**kwargs):
    print(args)
    print(kwargs)

func(name="bingbing")

# 装饰器函数
def outer(fn):
    def inner(*args,**kwargs):
        print("登录。。。。")
        fn(*args,**kwargs)
    return inner

ot = outer(func)
ot('susu',name="bingbing") #susu以元祖的形式传递给args，name="bingbing"以键=值的形式传递给kwargs
print("-----------------")
# 多个装饰器
def decol(fn):
    def inner():
        return " hahaha "+fn()+" hehehe "
    return inner
def decol2(fn):
    def inner():
        return " 好好好 "+fn()+" 非常优秀 "
    return inner

# 被装饰的函数
@decol
@decol2
def t1():
    return "冰冰老师很漂亮"
print(t1())
# hahaha  好好好 冰冰老师很漂亮 非常优秀  hehehe
# 多个装饰器，离函数最近的装饰器先装饰，然后外面的装饰器再进行装饰，有内到外
